/**
 * @author: 微若蜉蝣
 * @Blog :https://fuyouplus.cn
 * @Date :2022/7/25
 * @description: 手写数组所有方法
 */
let arr = [1, 2, 3, 4, 5]

Array.prototype.myForEach = function myForEach(func) {
  const arr = this
  const len = this.length
  for (let i = 0; i < len; i++) {
    func(arr[i], i, arr)
  }
}

//test

// arr.myForEach((item, index, arr) => {
//   console.log(item)
//   console.log(index)
// })

Array.prototype.myFilter = function myFilter(func) {
  const arr = this
  const len = this.length
  const res = []
  for (let i = 0; i < len; i++) {
    func(arr[i], i, arr) && res.push(arr[i])
  }
  return res
}

//test
// const newArr = arr.myFilter(item => item > 2)
// console.log(newArr)

Array.prototype.mySome = function mySome(func) {
  const arr = this
  const len = this.length
  for (let i = 0; i < len; i++) {
    if (func(arr[i], i, arr)) return true
  }
  return false
}

//test
// console.log(arr.mySome(item => item == 7))

Array.prototype.myEvery = function myEvery(func) {
  const arr = this
  const len = this.length
  for (let i = 0; i < len; i++) {
    if (!func(arr[i], i, arr)) return false
  }
  return true
}
//test
// console.log(arr.myEvery(item => item == 7))
Array.prototype.myPush = function myPush(...args) {
  const arr = this
  const len = arr.length
  const argsLen = args.length

  for (let i = 0; i < argsLen; i++) {
    arr[len + i] = args[i]
  }
  return arr.length
}
//test
// console.log(arr.myPush(6, 7, 8))
// console.log(arr)

Array.prototype.myDeepCopy = function myDeepCopy() {
  function deepCopy(target) {
    if (typeof target != "object" || target == null) {
      return target
    }
    const data = Array.isArray(target) ? [] : {}

    for (let key in target) {
      if (target.hasOwnProperty(key)) {
        data[key] = typeof target == "object" ? deepCopy(target[key]) : target[key]
      }
    }
    return data
  }
  return deepCopy(this)
}
//test
// const arr2 = [{ name: [1, 2, 3] }, { name: [4, 5, 6] }]
// const arr2 = [1, 2, 3, 4]
// const arr3 = arr2.myDeepCopy()
// arr2[0] = 99
// console.log(arr3)
// console.log(arr2.myDeepCopy())

Array.prototype.myConcat = function myConcat(...args) {
  const arr = this
  const res = []
  res.myPush(...arr)
  let arraysIndex = 0
  let oneArrayIndex = 0
  let resArrayIndex = res.length

  while (oneArrayIndex <= args[arraysIndex]?.length - 1) {
    if (oneArrayIndex == args[arraysIndex].length - 1) {
      res[resArrayIndex] = args[arraysIndex][oneArrayIndex]
      oneArrayIndex = 0
      arraysIndex++
    } else {
      res[resArrayIndex] = args[arraysIndex][oneArrayIndex]
      oneArrayIndex++
    }
    resArrayIndex++
  }

  return res
}
//test
// let arr2 = arr.myConcat([6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17])
// console.log("arr2=", arr2)

Array.prototype.myIndexOf = function myIndexOf(value, findIndex, strictMode = true) {
  const arr = this
  const len = arr.length

  if (findIndex) {
    if (findIndex > len) return -1
    if (findIndex < 0) {
      findIndex = len + findIndex
      if (findIndex < 0) {
        for (let i = 0; i < len; i++) {
          if (strictMode ? arr[i] === value : arr[i] == value) return i
        }
      } else {
        for (let i = findIndex; i < len; i++) {
          if (strictMode ? arr[i] === value : arr[i] == value) return i
        }
      }
    } else {
      for (let i = findIndex; i < len; i++) {
        if (strictMode ? arr[i] === value : arr[i] == value) return i
      }
    }
  } else {
    for (let i = 0; i < len; i++) {
      if (strictMode ? arr[i] === value : arr[i] == value) return i
    }
  }
  return -1
}
//test
// console.log(arr.myIndexOf(1))
// console.log(arr.myIndexOf("1", null, false))
// console.log(arr.myIndexOf(1, 1)) - 1
// console.log(arr.myIndexOf("1", 0, false))

Array.prototype.myIncludes = function myIncludes(value, findIndex, strictMode = true) {
  const arr = this
  const len = arr.length

  if (findIndex) {
    if (findIndex > len) return false
    if (findIndex < 0) {
      findIndex = len + findIndex
      if (findIndex < 0) {
        return arr.mySome(item => (strictMode ? item === value : item == value))
      } else {
        for (let i = findIndex; i < len; i++) {
          if (strictMode ? arr[i] === value : arr[i] == value) return true
        }
      }
    } else {
      for (let i = findIndex; i < len; i++) {
        if (strictMode ? arr[i] === value : arr[i] == value) return true
      }
    }
  } else {
    return arr.mySome(item => (strictMode ? item === value : item == value))
  }
  return false
}
//test
// console.log(arr.myIncludes(1)) //true
// console.log(arr.myIncludes("1", null, false)) //true
// console.log(arr.myIncludes(1, 1)) //false
// console.log(arr.myIncludes("1", 0, false)) //true

Array.prototype.mySplice = function mySplice(startIndex, count) {
  const arr = this
  const len = arr.length
  const res = []
  const filterArr = []
  const endIndex = startIndex + count

  for (let i = 0; i < len; i++) {
    if (i >= startIndex && i < endIndex) {
      res.myPush(arr[i])
    } else {
      filterArr.myPush(arr[i])
    }
  }

  arr.length = len - count

  for (let i = 0; i < filterArr.length; i++) {
    arr[i] = filterArr[i]
  }

  return res
}
//test
// let res2 = arr.mySplice(0, 3)

// console.log(res2)

// console.log(res2 === arr) //false

// console.log(arr)

Array.prototype.mySlice = function mySlice(startIndex = 0, endIndex = this.length) {
  const arr = this
  const len = arr.length
  const res = []
  startIndex = startIndex < 0 ? len + startIndex : startIndex
  if (startIndex > len) return res
  if (endIndex > len) {
    endIndex = len
  } else {
    endIndex = endIndex < 0 ? len + endIndex : endIndex
  }

  for (let i = startIndex; i < endIndex; i++) {
    res.myPush(arr[i])
  }
  return res
}
//test
// let res2 = arr.mySlice(1, 3)

// console.log(res2)

// console.log(res2 === arr) //false

// console.log(arr)

Array.prototype.myReverse = function myReverse() {
  const len = this.length
  const arr = this
  const middleNum = Math.floor(len / 2)
  const rightArr = arr.myFilter((_, index) =>
    len % 2 == 0 ? index >= middleNum : index > middleNum
  )

  for (let i = len - 1, j = 0; i > middleNum - 1; i--, j++) {
    arr[i] = arr[j]
  }
  for (let i = rightArr.length - 1, j = 0; i >= 0; i--, j++) {
    arr[j] = rightArr[i]
  }
  return arr
}
//test
// arr = [1, 2, 3, 4, 5] //奇数
// arr = [1, 2, 3, 4, 5, 6] //偶数

// let arr2 = JSON.parse(JSON.stringify(arr))
// let res = arr.myReverse()

// console.log("arr2=", arr2)
// console.log("res=", res)
// console.log(arr === res)

Array.prototype.myFill = function myFill(value) {
  const arr = this
  const len = arr.length
  for (let i = 0; i < len; i++) {
    arr[i] = value
  }
  return this
}
//test
// const arr2 = new Array(5)
// const res = arr2.myFill({ name: "zz" })
// arr2[0].name = "11"
// console.log(arr2)
// console.log(res)
// console.log(res === arr2)

Array.prototype.myFind = function myFind(func) {
  const arr = this
  const len = arr.length

  for (let i = 0; i < len; i++) {
    if (func(arr[i], i, arr)) return arr[i]
  }
  return undefined
}

//test
// const res = arr.myFind(item => item == 33)
// console.log(res)

Array.prototype.myFindIndex = function myFindIndex(func) {
  const arr = this
  const len = arr.length

  for (let i = 0; i < len; i++) {
    if (func(arr[i], i, arr)) return i
  }
  return -1
}

//test
// const res = arr.myFindIndex(item => item == 3)
// console.log(res)

Array.prototype.myAt = function myAt(index) {
  const arr = this
  return index >= 0 ? arr[index] : arr[arr.length + index]
}
//test
// console.log(arr.myAt(2)) //3
// console.log(arr.myAt(-1)) //5

Array.prototype.myFindLast = function myFindLast(func) {
  const arr = this
  const filterArr = arr.myFilter((item, index, arr) => func(item, index, arr))
  return filterArr.myAt(-1)
}

//test
// const res = arr.myFindLast(item => item > 3)
// console.log(res)

Array.prototype.myFindLastIndex = function myFindLastIndex(func) {
  const arr = this
  const len = arr.length
  const filterItemIndexArr = []

  for (let i = 0; i < len; i++) {
    if (func(arr[i], i, arr)) {
      filterItemIndexArr.myPush(i)
    }
  }

  return filterItemIndexArr.length ? filterItemIndexArr.myAt(-1) : -1
}

//test
// const res = arr.myFindLastIndex(item => item > 3)
// console.log(arr)
// console.log(res)

Array.prototype.myJoin = function myJoin(concatValue) {
  const arr = this
  const len = arr.length
  let res = ""
  for (let i = 0; i < len; i++) {
    res += i < len - 1 ? arr[i] + concatValue : arr[i] + ""
  }
  return res
}
//test
// const arr2 = ["a", "b", "c"]
// const res = arr2.myJoin("-")
// console.log(res)

Array.prototype.myKeys = function myKeys() {
  const arr = this
  const len = arr.length
  const res = []
  for (let i = 0; i < len; i++) {
    res.myPush(i)
  }
  return res
}
//test
// const res = arr.myKeys()
// console.log(res)

Array.prototype.myLastIndexOf = function myLastIndexOf(value) {
  const arr = this
  return arr.myFindLastIndex(item => item === value)
}
//test
// const animals = ["Dodo", "Tiger", "Penguin", "Dodo"]
// console.log(animals.myLastIndexOf("Dodo"))
// // expected output: 3

// console.log(animals.myLastIndexOf("Tiger"))
// // expected output: 1

Array.prototype.myMap = function myMap(func) {
  const arr = this
  const res = []
  arr.myForEach((item, index, arr) => {
    res.myPush(func(item, index, arr))
  })
  return res
}
//test
// const array1 = [1, 4, 9, 16]

// // pass a function to map
// const map1 = array1.myMap(x => x * 2)

// console.log(map1)
// // expected output: Array [2, 8, 18, 32]

Array.prototype.myEntries = function myEntries() {
  const arr = this
  const len = arr.length
  const iterator = {
    index: 0,
    [Symbol.iterator]() {
      return this
    },
    next() {
      const res = { value: [this.index, arr[this.index]], done: this.index >= len ? true : false }
      this.index++
      return res
    },
  }

  return iterator
}
//test
// const array1 = ["a", "b", "c"]

// const iterator1 = array1.myEntries()
// console.log(iterator1.next())
// console.log(iterator1.next())
// console.log(iterator1.next())
// console.log(iterator1.next())
// console.log(iterator1.next())
// console.log("----------------")
// for (let key of iterator1) {
//   console.log(key)
// }

Array.prototype.myUnique = function myUnique(key) {
  //扩展函数 去重
  const arr = this
  const res = []
  if (key) {
    arr.myForEach(item => {
      if (res.myFindIndex(item2 => item2[key] === item[key]) == -1) {
        res.myPush(item)
      }
    })
  } else {
    arr.myForEach(item => {
      if (!res.myIncludes(item)) {
        res.myPush(item)
      }
    })
  }
  return res
}
//test
// const testArr = [1, 1, 2, 3, 4, 4, 4, 5, 5, 6, 6]
// console.log(testArr.myUnique())
// const testArr = [{ name: "zz" }, { name: "zz" }, { name: "aa" }, { name: "bb" }, { name: "bb" }]
// console.log(testArr.myUnique("name"))

Array.prototype.myGroup = function myGroup(func) {
  const arr = this
  const res = {}

  arr.myForEach(item => {
    const keyVal = func(item)
    if (!res[keyVal]) {
      res[keyVal] = []
    }
    res[keyVal].myPush(item)
  })

  return res
}
//test
// const inventory = [
//   { name: "asparagus", type: "vegetables", quantity: 5 },
//   { name: "bananas", type: "fruit", quantity: 0 },
//   { name: "goat", type: "meat", quantity: 23 },
//   { name: "cherries", type: "fruit", quantity: 5 },
//   { name: "fish", type: "meat", quantity: 22 },
// ]

// console.log(inventory.myGroup(({ type }) => type))

// function myCallback({ quantity }) {
//   return quantity > 5 ? "ok" : "restock"
// }
// console.log(inventory.myGroup(myCallback))

Array.prototype.myGroupToMap = function myGroupToMap(func) {
  const arr = this
  const res = new Map()

  arr.myForEach(item => {
    const keyVal = func(item)
    if (!res.has(keyVal)) {
      res.set(keyVal, [])
    }
    res.get(keyVal).myPush(item)
  })

  return res
}
//test
// const inventory = [
//   { name: "asparagus", type: "vegetables", quantity: 9 },
//   { name: "bananas", type: "fruit", quantity: 5 },
//   { name: "goat", type: "meat", quantity: 23 },
//   { name: "cherries", type: "fruit", quantity: 12 },
//   { name: "fish", type: "meat", quantity: 22 },
// ]
// const restock = { restock: true }
// const sufficient = { restock: false }
// const result = inventory.myGroupToMap(({ quantity }) => (quantity < 6 ? restock : sufficient))
// console.log(result.get(restock))
// expected output: Array [Object { name: "bananas", type: "fruit", quantity: 5 }]

Array.prototype.myPop = function myPop() {
  const arr = this
  const len = arr.length
  const res = arr[len - 1]

  arr.length = len - 1

  return res
}

//test
// const plants = ["broccoli", "cauliflower", "cabbage", "kale", "tomato"]

// console.log(plants.myPop())
// // expected output: "tomato"

// console.log(plants)
// // expected output: Array ["broccoli", "cauliflower", "cabbage", "kale"]

// plants.myPop()

// console.log(plants)
// // expected output: Array ["broccoli", "cauliflower", "cabbage"]

Array.prototype.myShift = function myShift() {
  const arr = this
  const res = arr[0]
  const lastItem = arr.myPop()
  const len = arr.length

  for (let i = 0; i < len; i++) {
    if (i === len - 1) {
      arr[i] = lastItem
    } else {
      arr[i] = arr[i + 1]
    }
  }

  return res
}

//test
// const array1 = [1, 2, 3]

// const firstElement = array1.myShift()

// console.log(array1)
// // expected output: Array [2, 3]

// console.log(firstElement)
// // expected output: 1

Array.prototype.myUnshift = function myUnshift(...args) {
  const arr = this
  const copyArr = arr.myDeepCopy()
  const totalLen = arr.length + args.length

  for (let i = 0, j = 0; i < totalLen; i++, j++) {
    if (i == args.length) {
      j = 0
    }
    if (i >= args.length) {
      arr[i] = copyArr[j]
    } else {
      arr[i] = args[j]
    }
  }

  return totalLen
}
//test
// const array1 = [1, 2, 3]

// console.log(array1.myUnshift(4, 5))
// // expected output: 5

// console.log(array1)
// // expected output: Array [4, 5, 1, 2, 3]

Array.prototype.myValues = function myValues() {
  const arr = this
  const len = arr.length
  const iterator = {
    index: 0,
    [Symbol.iterator]() {
      return this
    },
    next() {
      const res = { value: [this.index, arr[this.index]], done: this.index >= len ? true : false }
      this.index++
      return res
    },
  }

  return iterator
}

//test
// const array1 = ["a", "b", "c"]
// const iterator = array1.myValues()
// console.log(iterator)
// for (let key of iterator) {
//   console.log(key)
// }

// // expected output: "a"
// // expected output: "b"
// // expected output: "c"
// const arr2 = ["a", "b", "c", "d", "e"]
// const iterator = arr2.myValues()
// for (let letter of iterator) {
//   console.log(letter)
// } //"a" "b" "c" "d" "e"
// for (let letter of iterator) {
//   console.log(letter)
// } //undefined

Array.prototype.myToString = function myToString() {
  const arr = this
  return arr.myJoin(",")
}

//test
// const array1 = [1, 2, "a", "1a"]
// console.log(array1.toString())
// // expected output: "1,2,a,1a"

Array.prototype.mySort = function mySort(func) {
  const arr = this
  const len = arr.length
  const res = []
  for (let i = 0; i < len; i++) {
    let min = arr[i]
    let minIndex = i
    for (let j = i + 1; j < len; j++) {
      if (func(arr[j], arr[minIndex])) {
        //如果后一个大于前一个
        min = arr[j] //把后一个赋值给min
        minIndex = j //把后一个的index赋值给minIndex
      }
    }
    res.myPush(min)
    arr[minIndex] = arr[i]
  }
  return res
}
//test
// const arr2 = [1, 3, 8, 5, 4, 6, 7, 2, 9]
// console.log(arr2.mySort((a, b) => b > a))
// const arr2 = [{ age: 11 }, { age: 9 }, { age: 22 }, { age: 10 }, { age: 8 }]
// console.log(arr2.mySort((a, b) => b.age < a.age))

Array.prototype.myReduce = function myReduce(...args) {
  const [func, initialValue] = args
  const arr = this
  const len = arr.length
  let previousValue = args.length == 2 ? initialValue : arr[0]

  for (let i = args.length == 2 ? 0 : 1; i < len; i++) {
    previousValue = func(previousValue, arr[i], i, arr)
  }
  return previousValue
}
//test
// const array1 = [1, 2, 3, 4]
// const initialValue = 0
// const sumWithInitial = array1.myReduce(
//   (previousValue, currentValue) => previousValue + currentValue,
//   initialValue
// )
// console.log(sumWithInitial) //10

// let initialValue = 0
// let sum = [{ x: 1 }, { x: 2 }, { x: 3 }]
// const res = sum.myReduce(function (previousValue, currentValue) {
//   return previousValue + currentValue.x
// }, initialValue)

// console.log(res) // logs 6

Array.prototype.myReduceRight = function myReduceRight(...args) {
  const [func, initialValue] = args
  const arr = this
  const len = arr.length
  let accumulator = args.length == 2 ? initialValue : arr[len - 1]

  for (let i = args.length == 2 ? len - 1 : len - 2; i >= 0; i--) {
    accumulator = func(accumulator, arr[i], i, arr)
  }
  return accumulator
}
//test
// const array1 = [
//   [0, 1],
//   [2, 3],
//   [4, 5],
// ]

// const result = array1.myReduceRight((accumulator, currentValue) => accumulator.concat(currentValue))

// console.log(result)
// // expected output: Array [4, 5, 2, 3, 0, 1]

// var sum = [0, 1, 2, 3].reduceRight(function (a, b) {
//   return a + b
// })
// console.log(sum) // sum is 6

Array.prototype.myCopyWithin = function myCopyWithin(target, start = 0, end = this.length) {
  const arr = this
  const len = arr.length
  target = target < 0 ? len + target : target
  start = start < 0 ? len + start : start
  end = end < 0 ? len + end : end
  const copyArr = arr.mySlice(start, end)

  for (let i = 0, j = target; i < copyArr.length; i++, j++) {
    arr[j] = copyArr[i]
  }

  return arr
}
//test
// const array1 = ["a", "b", "c", "d", "e"]
// // copy to index 0 the element at index 3
// console.log(array1.myCopyWithin(0, 2, 4))
// // expected output: Array ["d", "b", "c", "d", "e"]
// // copy to index 1 all elements from index 3 to the end
// console.log(array1.myCopyWithin(1, 3))
// // expected output: Array ["d", "d", "e", "d", "e"]
Array.myIsArray = function myIsArray(value) {
  return (
    Object.prototype.toString.call(value) == "[object Array]" &&
    typeof value == "object" &&
    value.hasOwnProperty("length") &&
    value.constructor === Array &&
    value.__proto__ === Array.prototype
  )
}
//test
//类数组 || 伪数组
// console.log("arr=", arr)
// let fakeArray = {
//   0: "a",
//   1: "b",
//   length: 2,
// }
// fakeArray.constructor = Array
// fakeArray.__proto__ = Array.prototype
// let fakeArray2 = Array.from(fakeArray)
// fakeArray2[0] = 22
// console.log(fakeArray)
// console.log(fakeArray2)
// console.log(fakeArray === fakeArray2)
// console.log(Array.myIsArray(fakeArray2))
// fakeArray.push("c")
// console.log("fakeArray=", fakeArray)
// console.log("pop=", fakeArray.pop())
// console.log("fakeArray2=", fakeArray)
// console.log(Array.myIsArray(fakeArray))
// // expected output: false
Array.prototype.myFlat = function myFlat(depth = 1) {
  const arr = this
  // arr.myForEach(item => {
  //   if (Array.myIsArray(item)) {
  //     res = res.myConcat(item.myFlat())
  //   } else {
  //     res.myPush(item)
  //   }
  // })
  // return res

  return depth
    ? arr.myReduce((acc, cur) => {
        if (Array.myIsArray(cur)) {
          acc.myPush(...cur.myFlat(depth - 1))
        } else {
          acc.myPush(cur)
        }
        return acc
      }, [])
    : arr.mySlice()
}
//test
// const arr2 = [1, 2, 3, [4, 5, [6, [7]]], [8, [9, 10, [11, 12]]]]
// const arr3 = [1, 2, 3, [4, 5, [6, [7]]], [8, [9, 10, [11, 12]]]]
// console.log("arr2=", arr2)
// console.log(arr2.myFlat(Infinity))
// console.log(arr3.flat(Infinity))

Array.prototype.myFlatMap = function myFlatMap(...args) {
  const [func, thisArg] = args
  const arr = this
  const len = arr.length
  let res = []

  for (let i = 0; i < len; i++) {
    if (Array.myIsArray(arr[i])) {
      res = res.myConcat(arr[i].myFlat())
    } else {
      let item = args.length == 2 ? func.call(thisArg, arr[i], i, arr) : func(arr[i], i, arr)
      arr[i] = Array.myIsArray(item) ? item.myAt(0) : item
      res.myPush(arr[i])
    }
  }

  return res
}
//test
// const arr1 = [1, 2, [3], [4, 5], 6, []]

// const flattened = arr1.myFlatMap(num => num)

// console.log(flattened)
// expected output: Array [1, 2, 3, 4, 5, 6]

// const arr1 = [1, 2, 3, 4]
// console.log(arr1.myFlatMap(x => [[x * 2]]))
// [[2], [4], [6], [8]]

Array.myFrom = function myFrom(...args) {
  const [arrayLike, mapFn, thisArg] = args
  const res = []
  for (let value of arrayLike) {
    res.myPush(
      args.length == 2 ? (args.length == 3 ? mapFn.call(thisArg, value) : mapFn(value)) : value
    )
  }

  return res
}
//test
// console.log(Array.myFrom("foo"))
// // expected output: Array ["f", "o", "o"]

// console.log(Array.myFrom([1, 2, 3], x => x + x))
// // expected output: Array [2, 4, 6]

Array.myOf = function myOf(...args) {
  return args
}
//test
// console.log(Array.myOf(7)) // [7]
// console.log(Array.myOf(1, 2, 3)) // [1, 2, 3]
